package org.budo.compress.zip;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import org.budo.compress.AbstractCompressor;
import org.budo.support.lang.util.FileUtil;
import org.budo.support.lang.util.IoUtil;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 */
@Slf4j
public class ZipCompressor extends AbstractCompressor {
    private static final String GBK = "GBK";

    private static final String UTF_8 = "UTF-8";

    @Override
    public void compress(File dir, File to) {
        try {
            String parentPath = "";
            ZipOutputStream zipTo = new ZipOutputStream(new FileOutputStream(to));

            this._zip(dir, zipTo, parentPath);

            zipTo.close(); // 手动关闭
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    private void _zipDir(File dir, ZipOutputStream zipTo, String parentPath) throws Throwable {
        String thisPath = parentPath.isEmpty() ? dir.getName() : parentPath + File.separator + dir.getName();

        File[] _listFiles = dir.listFiles();
        if (_listFiles == null || _listFiles.length == 0) {
            ZipEntry zipEntry = new ZipEntry(thisPath);
            zipTo.putNextEntry(zipEntry); // 空文件夹的处理
            zipTo.closeEntry(); // 结束Entry
            return;
        }

        for (File file : _listFiles) {
            this._zip(file, zipTo, thisPath);
        }
    }

    private void _zipFile(File file, ZipOutputStream zipTo, String parentPath) throws Throwable {
        String thisPath = parentPath + File.separator + file.getName();

        ZipEntry zipEntry = new ZipEntry(thisPath);
        zipTo.putNextEntry(zipEntry); // 向zip输出流中添加一个zip实体，构造器中name为zip实体的文件的名字

        FileInputStream inputStream = new FileInputStream(file);
        IoUtil.write(inputStream, zipTo, false, true);
        inputStream.close();

        zipTo.closeEntry(); // 结束写一个 Entry
    }

    private void _zip(File from, ZipOutputStream zipTo, String parentPath) throws Throwable {
        if (null == from || !from.exists()) {
            throw new RuntimeException("#61 from not exists, from=" + from);
        }

        if (from.isFile()) {
            this._zipFile(from, zipTo, parentPath);
            return;
        }

        if (from.isDirectory()) {
            this._zipDir(from, zipTo, parentPath);
            return;
        }

        throw new RuntimeException("#74 not file not dir, " + from);
    }

    @Override
    public void decompress(File from, File to) {
        try {
            _decompress(from, to, StandardCharsets.UTF_8, UTF_8);
        } catch (Throwable e1) { // lmw win10 haozip
            try {
                _decompress(from, to, StandardCharsets.ISO_8859_1, GBK); // 王超 Bandizip
            } catch (Throwable e2) {
                throw new RuntimeException("#91 e1=" + e1 + ", e2=" + e2, e2);
            }
        }
    }

    private void _decompress(File from, File to, Charset zipFileCharset, String zipEntryNameCharset) throws IOException {
        if (null == from || !from.exists() || from.length() < 1) {
            log.error("#106 _decompress, from is null or not exists or is empty, from=" + from + ", to=" + to);
            return;
        }

        ZipFile zipFile = new ZipFile(from, zipFileCharset);
        Enumeration<? extends ZipEntry> entries = zipFile.entries();
        while (entries.hasMoreElements()) {
            ZipEntry zipEntry = entries.nextElement();
            String zipEntryName = new String(zipEntry.getName().getBytes(zipFileCharset), zipEntryNameCharset);

            File toFile = new File(to.getCanonicalPath(), zipEntryName);
            if (zipEntry.isDirectory()) {
                toFile.mkdirs();
                continue;
            }

            FileUtil.createNewFile(toFile);

            InputStream inputStream = zipFile.getInputStream(zipEntry);
            IoUtil.write(inputStream, toFile);
        }

        zipFile.close();
    }
}
